package com.loveofsoftware.fotolab;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.Bitmap.Config;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;

import com.bala.R;
import com.loveofsoftware.fotolab.effects.DepthAndRotateByXEffect;
import com.loveofsoftware.fotolab.effects.DepthAndRotateByYEffect;
import com.loveofsoftware.fotolab.effects.DepthEffect;
import com.loveofsoftware.fotolab.effects.GrayScaleInitialEffect;
import com.loveofsoftware.fotolab.effects.RotateEffect;
import com.loveofsoftware.fotolab.effects.SketchInitialEffect;
import com.loveofsoftware.fotolab.effects.TintInitialEffect;
import com.loveofsoftware.fotolab.effects.TranslucentInitialEffect;

public class FotoView extends SurfaceView implements SurfaceHolder.Callback,
		SharedPreferences.OnSharedPreferenceChangeListener {
	
	static enum ImageState{INIT, FILE_OPENED, RESIZED, SAVED};

	public class StoreThread extends Thread {

		int bitmapWidth, bitmapHeight;
		int[] pixels;
		int[] workingPixels;

		private int mCanvasHeight = 1;
		private int mCanvasWidth = 1;

		/** Handle to the surface manager object we interact with */
		private SurfaceHolder mSurfaceHolder; // This will ne'er be null for a
		// thread
		private boolean mRun;
		private boolean stateChanged = true;
		private int mImgStartX;
		private int mImgStartY;


		private final int PAINT_FUNCTION = 1;
		private final int PICK_COLOR_FUNCTION = 2;
		private int pickedRedMin = -1;
		private int pickedGreenMin = -1;
		private int pickedBlueMin = -1;
		private int pickedRedMax = -1;
		private int pickedGreenMax = -1;
		private int pickedBlueMax = -1;
		
		

		public StoreThread(SurfaceHolder surfaceHolder, Context context) {
			mSurfaceHolder = surfaceHolder;
			mContext = context;
		}

		public StoreThread(StoreThread other) {
			mSurfaceHolder = other.mSurfaceHolder;
			bitmapWidth = other.bitmapWidth;
			bitmapHeight = other.bitmapHeight;
			pixels = other.pixels;
			workingPixels = other.workingPixels;
			mCanvasHeight = other.mCanvasHeight;
			mCanvasWidth = other.mCanvasWidth;
			mImgStartX = other.mImgStartX;
			mImgStartY = other.mImgStartY;
		}

		public void removePickedColor() {
			pickedRedMin = -1;
			System.out.println("Removed picked color");
		}

		public void pickColorFromImage(int index) {
			if (index < thread.workingPixels.length) {
				int color = thread.pixels[index];
				int red = Color.red(color);
				int green = Color.red(color);
				int blue = Color.red(color);

				if (-1 == pickedRedMin || red < pickedRedMin)
					pickedRedMin = red;
				if (red > pickedRedMax)
					pickedRedMax = red;
				if (-1 == pickedGreenMin || green < pickedGreenMin)
					pickedGreenMin = green;
				if (green > pickedGreenMax)
					pickedGreenMax = green;
				if (-1 == pickedBlueMin || red < pickedBlueMin)
					pickedBlueMin = blue;
				if (red > pickedBlueMax)
					pickedBlueMax = blue;
				// System.out.println("Picked color : ");
				return;
			}
			System.out.println("====ERROR : could not pick the color");

		}

		private synchronized void updatePixel(int index) {

			int color = thread.pixels[index];
			int red = Color.red(color);
			int green = Color.green(color);
			int blue = Color.blue(color);
			int alpha = 255;

			if (colorPreference.length() > 0) {
				if (colorPreference.equals("Red More")) {
					red = (int) (colorMagnifyfactor * red);
					if (colorMagnifyfactor > 0.8 * MAX_COLOR_MAGNIFY_FACTOR)
						red += colorMagnifyfactor * COLOR_MAGNIFY_MULTIPLIER;
					red = red > 255 ? 255 : red;
				} else if (colorPreference.equals("Green More")) {
					green = (int) (colorMagnifyfactor * green);
					if (colorMagnifyfactor > 0.8 * MAX_COLOR_MAGNIFY_FACTOR)
						green += colorMagnifyfactor * COLOR_MAGNIFY_MULTIPLIER;
					green = green > 255 ? 255 : green;
				} else if (colorPreference.equals("Blue More")) {
					blue = (int) (colorMagnifyfactor * blue);
					if (colorMagnifyfactor > 0.8 * MAX_COLOR_MAGNIFY_FACTOR)
						blue += colorMagnifyfactor * COLOR_MAGNIFY_MULTIPLIER;
					blue = blue > 255 ? 255 : blue;
				} else if (colorPreference.equals("Black and White")) {
					final float sat = GrayScaleInitialEffect.saturation;

					final float invSat = 1 - sat;
					final float R = 0.213f * invSat;
					final float G = 0.715f * invSat;
					final float B = 0.072f * invSat;

					red = (int) ((R + sat) * red + G * green + B * blue);
					blue = (int) (R * red + (G + sat) * green + B * blue);
					green = (int) (R * red + G * green + (B + sat) * blue);

				} else if (colorPreference.equals("Selective Image Color")) {
					if (-1 == pickedRedMin) {
						System.out.println("No picked color found");
						return;
					}

					if (red >= pickedRedMin * 0.7 && red <= pickedRedMax * 1.3
							&& green >= pickedGreenMin * 0.7
							&& green <= pickedGreenMax * 1.3
							&& blue >= pickedBlueMin * 0.7
							&& blue <= pickedBlueMax * 1.3)

					{
						// Alpha is already full
						// System.out.println("Found picked color : ");
						// Do we want to add some more effect?
					} else {
						System.out
								.println("Found picked color : current color not close = ("
										+ red
										+ ") ("
										+ green
										+ ") ("
										+ blue
										+ ")");
						return;
					}
				} else if (colorPreference.equals("Image Color")) {
					// By default it is Image Color
				} else {
					System.out
							.println("=========ERROR: unknown color option : "
									+ colorPreference);
				}
			}
			thread.workingPixels[index] = Color.argb(alpha, red, green, blue);
		}

		private synchronized void applyFunctionToPixel(int x, int y,
				int sideOfSquare, int radius, int function) {

			if (x >= 0 && x <= bitmapWidth && y >= 0 && y <= bitmapHeight) {
				for (int i = y - sideOfSquare / 2; i < y + sideOfSquare / 2; i++)
					for (int j = x - sideOfSquare / 2; j < x + sideOfSquare / 2; j++) {
						if (i >= 0 && i < bitmapHeight && j >= 0
								&& j < bitmapWidth) {
							// System.out.printf(" {%d,%d}", i, j);
							int index = i * bitmapWidth + j;
							if (index < thread.workingPixels.length)
								if (roundedRectanglePreference) {
									if (((i - y) * (i - y) + (j - x) * (j - x)) < radius
											* radius) {
										switch (function) {
										case PAINT_FUNCTION:
											updatePixel(index);
											break;
										case PICK_COLOR_FUNCTION:
											pickColorFromImage(index);
											break;
										default:
											break;
										}

									}
								} else {
									switch (function) {
									case PAINT_FUNCTION:
										updatePixel(index);
										break;
									case PICK_COLOR_FUNCTION:
										pickColorFromImage(index);
										break;
									default:
										break;
									}

								}
							// or else invalid pixel index
						}
					}
			} else
				System.out.println("The coordinates were outside the image");
		}

		public synchronized void restore(int[] inPixels, int[] inWorkingPixels,
				int width, int height) {

			if (null == inPixels)
				return; // Do nothing
			pixels = inPixels;
			workingPixels = inWorkingPixels;
			bitmapWidth = width;
			bitmapHeight = height;

			if (width <= 0 || height <= 0) {
				System.out
						.println("Strange : ERROR ERROR bitmap width/height is 0 - "
								+ width + "," + height);
				return;
			}
			if (null != workingPixels) {
				mImgStartX = (mCanvasWidth - bitmapWidth) / 2;
				mImgStartY = (mCanvasHeight - bitmapHeight) / 2;
				System.out.println("Restoring image : " + width + "," + height
						+ "; canvas: " + mCanvasWidth + "," + mCanvasHeight);
			}

		}

		public synchronized void increaseEffect(float deg) {

			if (initialImageEffectPreference.equals("BlackAndWhite")) {
				// GrayScaleInitialEffect.saturation = deg;
				setContrastAndBrightness(deg * 2 - 1, -1);
			} else if (initialImageEffectPreference.equals("Translucent")) {
				// TranslucentInitialEffect
			} else if (initialImageEffectPreference.equals("Sketchify")) {
				// SketchInitialEffect
			} else if (initialImageEffectPreference.equals("Tint")) {
				// TintInitialEffect
			} else if (initialImageEffectPreference.equals("Rotate")) {
				// RotateEffect
			} else if (initialImageEffectPreference.equals("TurnByX")) {
				// DepthAndRotateByXEffect
			} else if (initialImageEffectPreference.equals("TurnByY")) {
				// DepthAndRotateByYEffect
			} else if (initialImageEffectPreference.equals("Depth")) {
				// DepthEffect
			}

			applyEffect();

		}

		public synchronized void applyEffect() {
			if (null == workingPixels) {
				workingPixels = pixels;
			}

			System.out.println("Applying effect "
					+ initialImageEffectPreference + " to " + bitmapWidth + "x"
					+ bitmapHeight + ", pixels=" + pixels.length + ","
					+ workingPixels.length);

			Bitmap workingBMap = Bitmap.createBitmap(workingPixels,
					bitmapWidth, bitmapHeight, Config.ARGB_8888);

			if (initialImageEffectPreference.equals("BlackAndWhite")) {
				workingBMap = (new GrayScaleInitialEffect())
						.transform(workingBMap);
			} else if (initialImageEffectPreference.equals("Translucent")) {
				workingBMap = (new TranslucentInitialEffect())
						.transform(workingBMap);
			} else if (initialImageEffectPreference.equals("Sketchify")) {
				workingBMap = (new SketchInitialEffect())
						.transform(workingBMap);
			} else if (initialImageEffectPreference.equals("Tint")) {
				workingBMap = (new TintInitialEffect()).transform(workingBMap);

			} else if (initialImageEffectPreference.equals("Rotate")) {
				Bitmap originalBMap = Bitmap.createBitmap(pixels, bitmapWidth,
						bitmapHeight, Config.ARGB_8888);
				originalBMap = (new RotateEffect()).transform(originalBMap);
				pixels = new int[originalBMap.getWidth()
						* originalBMap.getHeight()];
				originalBMap.getPixels(pixels, 0, originalBMap.getWidth(), 0,
						0, originalBMap.getWidth(), originalBMap.getHeight());
				originalBMap = null;

				workingBMap = (new RotateEffect()).transform(workingBMap);

			} else if (initialImageEffectPreference.equals("TurnByX")) {
				Bitmap originalBMap = Bitmap.createBitmap(pixels, bitmapWidth,
						bitmapHeight, Config.ARGB_8888);
				originalBMap = (new DepthAndRotateByXEffect(mImgStartX,
						mImgStartY)).transform(originalBMap);
				pixels = new int[originalBMap.getWidth()
						* originalBMap.getHeight()];
				originalBMap.getPixels(pixels, 0, originalBMap.getWidth(), 0,
						0, originalBMap.getWidth(), originalBMap.getHeight());
				originalBMap = null;

				workingBMap = (new DepthAndRotateByXEffect(mImgStartX,
						mImgStartY)).transform(workingBMap);

			} else if (initialImageEffectPreference.equals("TurnByY")) {
				Bitmap originalBMap = Bitmap.createBitmap(pixels, bitmapWidth,
						bitmapHeight, Config.ARGB_8888);
				originalBMap = (new DepthAndRotateByYEffect(mImgStartX,
						mImgStartY)).transform(originalBMap);
				pixels = new int[originalBMap.getWidth()
						* originalBMap.getHeight()];
				originalBMap.getPixels(pixels, 0, originalBMap.getWidth(), 0,
						0, originalBMap.getWidth(), originalBMap.getHeight());
				originalBMap = null;

				workingBMap = (new DepthAndRotateByYEffect(mImgStartX,
						mImgStartY)).transform(workingBMap);

			} else if (initialImageEffectPreference.equals("Depth")) {
				Bitmap originalBMap = Bitmap.createBitmap(pixels, bitmapWidth,
						bitmapHeight, Config.ARGB_8888);
				originalBMap = (new DepthEffect()).transform(originalBMap);
				pixels = new int[originalBMap.getWidth()
						* originalBMap.getHeight()];
				originalBMap.getPixels(pixels, 0, originalBMap.getWidth(), 0,
						0, originalBMap.getWidth(), originalBMap.getHeight());
				originalBMap = null;
				workingBMap = (new DepthEffect()).transform(workingBMap);
			}

			bitmapWidth = workingBMap.getWidth();
			bitmapHeight = workingBMap.getHeight();
			workingPixels = new int[bitmapWidth * bitmapHeight];
			workingBMap.getPixels(workingPixels, 0, bitmapWidth, 0, 0,
					bitmapWidth, bitmapHeight);

			mImgStartX = (mCanvasWidth - bitmapWidth) / 2;
			mImgStartY = (mCanvasHeight - bitmapHeight) / 2;

			if (pixels.length != workingPixels.length) {
				// This is a size altering transform
				pixels = workingPixels;
				System.out
						.println("WARNING ************** SIZE ALTERING TRANSFORM NOT HANDLED PROPERLY ********* "
								+ initialImageEffectPreference);
				System.out
						.println("WARNING ************** LOSING ORIGINAL IMAGE *********");
			}
		}

		public synchronized String openImage(String imageName) {
			hasScaledToCanvas = false;
			FileInputStream in;
			BufferedInputStream buf;
			Bitmap bMap = null;
			try {
				if ((new File(imageName)).length() > 4 * 1024 * 1024) {
					return "File size is higher than 4MB: "
							+ ((new File(imageName)).length() / (1024 * 1024))
							+ "MB";
				}

				// if(Runtime.getRuntime().freeMemory() < 12*1024*1024)
				// {
				// return "Free Memory is less than 12MB : "+
				// (Runtime.getRuntime().freeMemory()/(1024.0*1024))+"MB";
				// }
				in = new FileInputStream(imageName);
				buf = new BufferedInputStream(in);
				if (buf.available() > 500 * 1024) {
					BitmapFactory.Options opts = new BitmapFactory.Options();
					opts.inSampleSize = (int) Math.sqrt(buf.available()
							/ (22.36 * 1024.0));
					bMap = BitmapFactory.decodeStream(buf, null, opts);
					Toast.makeText(mContext,
							"Resampled the image to fit memory",
							Toast.LENGTH_LONG).show();
				} else {
					bMap = BitmapFactory.decodeStream(buf);
				}

				System.out.println(bMap.getConfig());
				bMap = bMap.copy(Config.ARGB_8888, true);
				System.out.println("Initial effect is : "
						+ initialImageEffectPreference);

				bitmapHeight = bMap.getHeight();
				bitmapWidth = bMap.getWidth();
				pixels = new int[bitmapHeight * bitmapWidth];
				bMap.getPixels(pixels, 0, bitmapWidth, 0, 0, bitmapWidth,
						bitmapHeight);

				scaleTheImageIfNeeded(bMap);
				if (in != null) {
					in.close();
				}
				if (buf != null) {
					buf.close();
				}

				return null;

			} catch (Exception e) {
				Log.e("Error reading file", e.toString());
				return "Exception : " + e.toString();
			} catch (OutOfMemoryError e1) {
				return "Too big a file to open or too less memory on phone";
			}
		}

		boolean hasScaledToCanvas = false;

		private void scaleTheImageIfNeeded(Bitmap bMap) {
			if (hasScaledToCanvas)// Do it only once
				return;

			if (null == pixels)// There is no image opened i guess
				return;
			
			if (1 == mCanvasWidth || 1== mCanvasHeight)
				return;

			if (null == bMap) {
				bMap = Bitmap.createBitmap(pixels, bitmapWidth, bitmapHeight,
						Config.ARGB_8888);
			}
			
			hasScaledToCanvas = true;
			if (bitmapWidth > mCanvasWidth || bitmapHeight > mCanvasHeight) {
				if (bitmapHeight <= mCanvasWidth
						|| bitmapWidth <= mCanvasHeight) {
					String requiredModeStr = "landscape";
					if (mCanvasHeight > mCanvasWidth)
						requiredModeStr = "portrait";
					Toast.makeText(
							mContext,
							"It is recommended you turn the phone to "
									+ requiredModeStr
									+ " to better edit the picture",
							Toast.LENGTH_LONG).show();
				}

				System.out.println("Scaled the image [pre]: " + bitmapWidth
						+ "," + bitmapHeight);

				float r1 = mCanvasWidth / (float) bitmapWidth;
				float r2 = mCanvasHeight / (float) bitmapHeight;
				float ratio = r2 < r1 ? r2 : r1;
				bMap = Bitmap.createScaledBitmap(bMap,
						(int) (ratio * bitmapWidth),
						(int) (ratio * bitmapHeight), true);
				// Matrix matrix = new Matrix();
				//
				// matrix.postScale(ratio, ratio);
				// bMap = Bitmap.createBitmap(bMap, 0, 0, bitmapWidth,
				// bitmapHeight, matrix, true);

				bitmapHeight = bMap.getHeight();
				bitmapWidth = bMap.getWidth();
				System.out.println("Scaled the image [post]: " + bitmapWidth
						+ "," + bitmapHeight);

			}
			pixels = new int[bitmapHeight * bitmapWidth];
			bMap.getPixels(pixels, 0, bitmapWidth, 0, 0, bitmapWidth,
					bitmapHeight);

			workingPixels = null;

			applyEffect();
			mImgStartX = (mCanvasWidth - bitmapWidth) / 2;
			mImgStartY = (mCanvasHeight - bitmapHeight) / 2;

		}

		public synchronized void save(String filename) {
			if (null == workingPixels)
				return; // Nothing to do
			try {
				if (null == filename || filename.length() == 0) {
					System.out.println("ERROR: Did not select a filename");
					return;
				}
				if (!filename.endsWith(".png") && !filename.endsWith(".PNG")
						&& !filename.endsWith(".Png"))
					filename += ".png";
				FileOutputStream out = new FileOutputStream(filename);

				Bitmap bitmapToSave = null;

				bitmapToSave = Bitmap.createBitmap(bitmapWidth, bitmapHeight,
						Config.ARGB_8888);
				Canvas canvas = new Canvas(bitmapToSave);
				thread.internalGameRender(canvas, true);

				bitmapToSave.compress(Bitmap.CompressFormat.PNG, 90, out);
				out.close();
				Toast.makeText(mContext, "Saved", Toast.LENGTH_LONG).show();
			} catch (Exception e) {
				Toast.makeText(mContext, "Error Saving", Toast.LENGTH_LONG)
						.show();
				e.printStackTrace();
			}
		}

		@Override
		public void run() {
			while (mRun) {
				Canvas c = null;
				try {
					c = mSurfaceHolder.lockCanvas(null);
					synchronized (mSurfaceHolder) {
						if (stateChanged) {

							gameRender(c);
						}
					}
				} finally {
					// do this in a finally so that if an exception is thrown
					// during the above, we don't leave the Surface in an
					// inconsistent state
					if (c != null) {
						mSurfaceHolder.unlockCanvasAndPost(c);
					}
				}
				try {
					Thread.sleep(18);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

		public void setRunning(boolean b) {
			mRun = b;
		}

		/* Callback invoked when the surface dimensions change. */
		public void setSurfaceSize(int width, int height) {
			// synchronized to make sure these all change atomically
			synchronized (mSurfaceHolder) {
				mCanvasWidth = width;
				mCanvasHeight = height;

				scaleTheImageIfNeeded(null);
				if (null != workingPixels) {
					mImgStartX = (mCanvasWidth - bitmapWidth) / 2;
					mImgStartY = (mCanvasHeight - bitmapHeight) / 2;
				}

				System.out.println("The width of canvas is : " + mCanvasWidth);
				System.out
						.println("The height of canvas is : " + mCanvasHeight);
			}
		}

		private synchronized void gameRender(Canvas canvas) {

			internalGameRender(canvas, false);
		}

		private synchronized void internalGameRender(Canvas canvas,
				boolean forSave) {
			if (null == workingPixels || bitmapWidth <=0 || bitmapHeight <=0) {
				try {
					canvas.drawText(
							"Please open a image by selecting open from menu",
							0, mCanvasHeight / 2, getTextPaint());
				} catch (Exception e) {
				}
				return;
			}
			// Draw the background image. Operations on the Canvas
			// accumulate
			// so this is like clearing the screen.

			// TODO: see if this computation is needed
			mImgStartX = (mCanvasWidth - bitmapWidth) / 2;
			mImgStartY = (mCanvasHeight - bitmapHeight) / 2;

			Bitmap workingBMap = Bitmap.createBitmap(workingPixels,
					bitmapWidth, bitmapHeight, Config.ARGB_8888);

			canvas.drawColor(Color.WHITE);
			if (!forSave)
				canvas.drawBitmap(workingBMap, mImgStartX, mImgStartY,
						bitmapPaint);
			else
				canvas.drawBitmap(workingBMap, 0, 0, bitmapPaint);

			if (null != cardViews) {
				for (int i = 0; i < cardViews.length; i++) {
					if (!forSave)
						cardViews[i].onDraw(canvas);
					else
						cardViews[i].onDrawTranslated(canvas,
								thread.mImgStartX, thread.mImgStartY);
				}
			}
		}

	}

	/** Handle to the application context, used to e.g. fetch Drawables. */
	private Context mContext;

	/** Pointer to the text view to display "Paused.." etc. */
	// private TextView mStatusText;
	/** The thread that actually draws the animation */
	private StoreThread thread;

	private static final int PRESSURE_CONST_DP = 100;
	// a size of 0.2 should result in 0.5in figure
	private static final int SIZE_CONST_DP = 200;

	public static float colorMagnifyfactor = 1.5f;
	public static final float MAX_COLOR_MAGNIFY_FACTOR = 3.0f;
	public static final int COLOR_MAGNIFY_MULTIPLIER = 100;

	public static float mContrast = 0.5f;
	public static final float MAX_CONTRAST = 1.0f;

	public static float mBrightness = 0.5f;
	public static final float MAX_BRIGHTNESS = 1.0f;

	private CardView[] cardViews = null;
	private float mScale = -1;

	private static Paint bitmapPaint = new Paint();
	// private static final String AUTOSAVE_FILENAME =
	// Util.getFotolabDir()+"com.bala.autosave.png";

	boolean roundedRectanglePreference = true;
	public String colorPreference = "";
	public String initialImageEffectPreference = "";

	public FotoView(Context context, AttributeSet attrs) {
		super(context, attrs);

		// register our interest in hearing about changes to our surface
		SurfaceHolder holder = getHolder();
		holder.addCallback(this);

		// create thread only; it's started in surfaceCreated()
		thread = new StoreThread(holder, context);

		SharedPreferences prefs = PreferenceManager
				.getDefaultSharedPreferences(context.getApplicationContext());
		prefs.registerOnSharedPreferenceChangeListener(this);

		setFocusable(true); // make sure we get key events
	}

	private void applyFunctionToTouch(MotionEvent event, int function) {
		synchronized (thread.mSurfaceHolder) {
			int N = event.getHistorySize();

			float oldx = -1, oldy = -1;
			for (int i = 0; i < N; i++) {

				float x = event.getHistoricalX(i);
				float y = event.getHistoricalY(i);
				float size = event.getHistoricalSize(i);

				int pressure = (int) event.getHistoricalPressure(i);
				// System.out.println("pressure="+pressure+", size="+size);
				int sideOfSquare = pressure * PRESSURE_CONST_DP
						+ (int) (size * SIZE_CONST_DP);
				if (sideOfSquare <= 0)
					sideOfSquare = 10;
				sideOfSquare *= mScale;
				int radius = (int) (sideOfSquare / 2);
				if (i == 0
						|| (x - oldx) * (x - oldx) + (y - oldy) * (y - oldy) >= radius
								* radius) {
					thread.applyFunctionToPixel((int) x - thread.mImgStartX,
							(int) y - thread.mImgStartY, sideOfSquare, radius,
							function);
				} else
					System.out
							.println("ignoring as batch already had the touch x,y");
				oldx = x;
				oldy = y;
			}

			float x = event.getX();
			float y = event.getY();

			int pressure = (int) event.getPressure();
			float size = event.getSize();
			// System.out.println("pressure="+pressure+", size="+size);
			int sideOfSquare = pressure * PRESSURE_CONST_DP
					+ (int) (size * SIZE_CONST_DP);
			if (sideOfSquare <= 0)
				sideOfSquare = 10;
			sideOfSquare *= mScale;
			int radius = (int) (sideOfSquare / 2);
			if (oldx == -1
					|| (x - oldx) * (x - oldx) + (y - oldy) * (y - oldy) >= radius
							* radius) {
				thread.applyFunctionToPixel((int) x - thread.mImgStartX,
						(int) y - thread.mImgStartY, sideOfSquare, radius,
						function);
			} else
				System.out
						.println("ignoring as batch already had the touch x,y");

		}

	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {

		if (EffectActivity.detector.onTouchEvent(event))
			return true;
		if (null == thread.workingPixels)
			return true;
		if (null != cardViews) {
			for (int i = 0; i < cardViews.length; i++) {
				if (cardViews[i].onTouchEvent(event))
					return true;
			}
		}

		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			applyFunctionToTouch(event, thread.PICK_COLOR_FUNCTION);

			return true;
		}
		if (event.getAction() == MotionEvent.ACTION_UP
				|| event.getAction() == MotionEvent.ACTION_CANCEL) {
			thread.removePickedColor();
			return true;
		}

		applyFunctionToTouch(event, thread.PAINT_FUNCTION);
		return true;
	}

	public StoreThread getThread() {
		return thread;
	}

	@Override
	public void onWindowFocusChanged(boolean hasWindowFocus) {
		// if (!hasWindowFocus) Model.getModel().pause();
	}

	/* Callback invoked when the surface dimensions change. */
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
		thread.setSurfaceSize(width, height);
	}

	/*
	 * Callback invoked when the Surface has been created and is ready to be
	 * used.
	 */
	public void surfaceCreated(SurfaceHolder holder) {
		mScale = getContext().getResources().getDisplayMetrics().density;
		// start the thread here so that we don't busy-wait in run()
		// waiting for the surface to be created
		if (thread.getState() == Thread.State.NEW) {
			thread.setRunning(true);
			thread.start();
		} else if (thread.getState() == Thread.State.TERMINATED) {
			thread = new StoreThread(thread);
			thread.setRunning(true);
			thread.start();
		}
	}

	/*
	 * Callback invoked when the Surface has been destroyed and must no longer
	 * be touched. WARNING: after this method returns, the Surface/Canvas must
	 * never be touched again!
	 */
	public void surfaceDestroyed(SurfaceHolder holder) {
		// we have to tell thread to shut down & wait for it to finish, or else
		// it might touch the Surface after we return and explode
		boolean retry = true;
		thread.setRunning(false);
		while (retry) {
			try {
				thread.join();
				retry = false;
			} catch (InterruptedException e) {
			}
		}
	}

	public String currentImageFilename = null;

	public String openImage(String selectedImagePath) {

		getPrefs();
		System.out.println("Image to be opened : " + selectedImagePath);
		String result = getThread().openImage(selectedImagePath);
		if (null == result) {
			currentImageFilename = selectedImagePath;
			this.invalidate();
		}
		return result;
	}

	public void save(String filename) {
		System.out.println("Image to be saved to : " + filename);
		if (null != thread)
			thread.save(filename);
	}

	public void addCardView(CardView cardView) {
		if (null == cardViews) {
			cardViews = new CardView[1];
			cardViews[0] = cardView;
			return;
		}
		CardView[] newCardViews = new CardView[cardViews.length + 1];
		System.arraycopy(cardViews, 0, newCardViews, 0, cardViews.length);
		newCardViews[newCardViews.length - 1] = cardView;
		cardViews = newCardViews;

	}

	/**
	 * Manage preferences
	 */
	private void getPrefs() {
		// Get the xml/preferences.xml preferences
		SharedPreferences prefs = PreferenceManager
				.getDefaultSharedPreferences(mContext.getApplicationContext());
		roundedRectanglePreference = prefs.getBoolean("roundedRectangle", true);
		colorPreference = prefs.getString("colors", "Image Color");
		initialImageEffectPreference = prefs.getString("initialImageEffect",
				"BlackAndWhite");
	}

	public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
			String key) {
		Resources res = getResources();
		Toast.makeText(mContext, res.getString(R.string.helpMsgPrefSaved),
				Toast.LENGTH_LONG).show();
		getPrefs();
	}

	/**
	 * Contrast and brightness
	 * 
	 * @param contrast
	 * @param brightness
	 */
	public static void setContrastAndBrightness(float contrast, float brightness) {
		if (contrast >= -1)
			mContrast = contrast;
		if (brightness >= 0)
			mBrightness = brightness;
		System.out.println("Brightness=" + brightness + "," + mBrightness
				+ ", contrast=" + contrast + "," + mContrast);
		float scale = mContrast + 1.f;
		float translate = (-.5f * scale + .5f) * 255.f;
		float[] array = new float[] { scale, 0, 0, 0, translate, 0, scale, 0,
				0, translate, 0, 0, scale, 0, translate, 0, 0, 0, 1, 0 };

		scale = mBrightness;
		float[] brightnessArray = new float[] { scale, 0, 0, 0, 0, 0, scale, 0,
				0, 0, 0, 0, scale, 0, 0, 0, 0, 0, 1, 0 };

		ColorMatrix matrix = new ColorMatrix(array);
		matrix.postConcat(new ColorMatrix(brightnessArray));
		ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix);

		bitmapPaint.setColorFilter(filter);
	}

	/**
	 * For suspend and portrait to landscape mode
	 * 
	 * @param inState
	 */
	public boolean restore(Bundle inState) {
		synchronized (thread.mSurfaceHolder) {
			thread.restore(inState.getIntArray("PIXELS"),
					inState.getIntArray("WORKING_PIXELS"),
					inState.getInt("WORKING_BMAP_WIDTH"),
					inState.getInt("WORKING_BMAP_HEIGHT"));

			cardViews = (CardView[]) inState.getSerializable("CARD_VIEWS");
			mScale = inState.getFloat("SCALE");

			colorMagnifyfactor = inState.getFloat("COLOR_MAGNIFY_FACTOR");
			setContrastAndBrightness(inState.getFloat("CONTRAST"),
					inState.getFloat("BRIGHTNESS"));

			currentImageFilename = inState.getString("FILENAME");

			int len = inState.getInt("CARD_VIEWS_LENGTH");
			if (len > 0) {
				CardView[] newCardViews = new CardView[len];
				for (int i = 0; i < len; i++) {
					int color = inState.getInt("CARD_VIEW_TEXT_PAINT_" + i);
					Paint textPaint = new Paint();
					textPaint.setAntiAlias(true);
					textPaint.setColor(color);
					textPaint.setTextSize(12);

					newCardViews[i] = new CardView(mContext, textPaint,
							inState.getString("CARD_VIEW_TEXT_" + i),
							inState.getInt("CARD_VIEW_LEFT_" + i),
							inState.getInt("CARD_VIEW__TOP_" + i));
				}
			}
			roundedRectanglePreference = inState
					.getBoolean("roundedRectanglePreference");
			colorPreference = inState.getString("colorPreference ");
			initialImageEffectPreference = inState
					.getString("initialImageEffectPreference");

			return null != thread.workingPixels;
		}
	}

	public void onSaveInstanceState(Bundle outState) {

		if (null != thread) {
			synchronized (thread.mSurfaceHolder) {
				thread.setRunning(false);

				if (null != thread.pixels)
					outState.putIntArray("PIXELS", thread.pixels);
				if (null != thread.workingPixels)
					outState.putIntArray("WORKING_PIXELS", thread.workingPixels);
				if (null != thread.workingPixels) {
					outState.putInt("WORKING_BMAP_HEIGHT", thread.bitmapHeight);
					outState.putInt("WORKING_BMAP_WIDTH", thread.bitmapWidth);
				}

			}

			if (null != cardViews) {
				outState.putInt("CARD_VIEWS_LENGTH", cardViews.length);
				for (int i = 0; i < cardViews.length; i++) {
					outState.putInt("CARD_VIEW_TEXT_PAINT_" + i,
							cardViews[i].mPaint.getColor());
					outState.putString("CARD_VIEW_TEXT_" + i,
							cardViews[i].mText);
					outState.putInt("CARD_VIEW_LEFT_" + i,
							cardViews[i].getPosition().x);
					outState.putInt("CARD_VIEW__TOP_" + i,
							cardViews[i].getPosition().y);
				}
			}
			outState.putFloat("SCALE", mScale);
			outState.putString("FILENAME", currentImageFilename);
			outState.putFloat("COLOR_MAGNIFY_FACTOR", colorMagnifyfactor);
			outState.putFloat("CONTRAST", mContrast);
			outState.putFloat("BRIGHTNESS", mBrightness);
			outState.putBoolean("roundedRectanglePreference",
					roundedRectanglePreference);
			outState.putString("colorPreference ", colorPreference);
			outState.putString("initialImageEffectPreference",
					initialImageEffectPreference);
		}
	}

	public Paint getTextPaint() {
		Paint textPaint = new Paint();
		textPaint.setAntiAlias(true);

		int color = Color.BLACK;

		if (colorPreference.length() > 0) {
			if (colorPreference.equals("Red More")) {
				color = Color.RED;
			} else if (colorPreference.equals("Green More")) {
				color = Color.GREEN;
			} else if (colorPreference.equals("Blue More")) {
				color = Color.BLUE;
			}
		}
		textPaint.setColor(color);
		textPaint.setTextSize(12);
		return textPaint;
	}
}